﻿using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Threading;
using System.Linq;
using System.Diagnostics;

namespace block_explorer
{
    class JobScheduler : IComponent
    {
        SectorComparer mComparer;

        bool mNeedsSort;
        List<MapSector> mQueue;
        List<Thread> mThreads;

        HashSet<SectorAddress> mWaitingSectors;
        HashSet<SectorAddress> mProcessingSectors;

        bool mAllowRun;

        const int MAX_THREADS = 4;

        public int QueuedJobs { get { return mWaitingSectors.Count; } }
        public int RunningJobs { get { return mProcessingSectors.Count; } }
        public bool AllowRun { get { return mAllowRun; } }

        public JobScheduler(SectorComparer comparer)
        {
            mComparer = comparer;
            mWaitingSectors = new HashSet<SectorAddress>();
            mProcessingSectors = new HashSet<SectorAddress>();
            mQueue = new List<MapSector>();
            mThreads = new List<Thread>();
            mAllowRun = true;
            mNeedsSort = false;
        }

        public void Initialize(GraphicsDevice device, ContentManager content)
        {
            while (mThreads.Count < MAX_THREADS)
            {
                SectorBuilder builder = new SectorBuilder(this, device);
                Thread buildThread = new Thread(new ThreadStart(builder.Run));

                try
                {
                    buildThread.Start();
                    mThreads.Add(buildThread); // add it to the list of alive ones
                }
                catch (ThreadStartException e)
                {
                    BlockExplorer.Console.AddLine(e.ToString());
                }
                catch (ThreadInterruptedException e)
                {
                    BlockExplorer.Console.AddLine(e.ToString());
                }
            }
        }

        public void Destroy()
        {
            mAllowRun = false;

            foreach (Thread thread in mThreads)
            {
                thread.Abort();
            }
        }

        public void Update(GameTime gameTime)
        {
        }

        public bool QueueBuild(MapSector sector)
        {
            //return true;
            lock (this)
            {
                // is the sector already queued?
                if (mWaitingSectors.Contains(sector.Address))
                    return false;

                // does the sector already have a thread?
                if (mProcessingSectors.Contains(sector.Address))
                    return false;

                mQueue.Add(sector);
                mWaitingSectors.Add(sector.Address);

                mNeedsSort = true;
                // this is slow and causing jitters
                //mQueue.Sort(mComparer);

                return true;
            }
        }

        public MapSector GetJob()
        {
            lock (this)
            {
                MapSector nextSector = null;

                if (mQueue.Count <= 0)
                    return null;

                if (mNeedsSort)
                {
                    mQueue.Sort(mComparer);
                    mNeedsSort = false;
                }

                // first pick only visible sectors in the queue
                foreach (var sector in mQueue)
                {
                    if (sector.Visible)
                    {
                        nextSector = sector;
                        break;
                    }
                }

                if (nextSector == null)
                    nextSector = mQueue.ElementAt(0);

                mQueue.Remove(nextSector);
                mWaitingSectors.Remove(nextSector.Address);
                mProcessingSectors.Add(nextSector.Address);

                return nextSector;
            }
        }

        public void NotifyJobDone(SectorAddress address)
        {
            lock (this)
            {
                Debug.Assert(mProcessingSectors.Contains(address));
                mProcessingSectors.Remove(address);
            }
        }

        private class SectorBuilder
        {
            JobScheduler mScheduler;
            GraphicsDevice mDevice;

            public SectorBuilder(JobScheduler scheduler, GraphicsDevice device)
            {
                mScheduler = scheduler;
                mDevice = device;
            }

            public void Run()
            {
                try
                {
                    while (mScheduler.AllowRun)
                    {
                        MapSector sector = mScheduler.GetJob();
                        if (sector != null)
                        {
                            if (!sector.Built)
                                Build(sector);
                            else if (sector.NeedsFullLightsRefresh)
                                FullRefreshLights(sector);
                            else if (sector.NeedsMeshRefresh)
                                RefreshMesh(sector);
                            else if (sector.NeedsLightsRefresh)
                                RefreshLights(sector);

                            mScheduler.NotifyJobDone(sector.Address);
                        }
                        Thread.Sleep(1);
                    }
                }
                catch (ThreadAbortException e)
                {
                    return;
                }
            }

            private void Build(MapSector sector)
            {
                sector.CacheBlocks();
                //sector.RefreshLights();
                sector.RefreshMesh(mDevice);
            }

            private void RefreshMesh(MapSector sector)
            {
                sector.RefreshMesh(mDevice);
            }

            private void RefreshLights(MapSector sector)
            {
                //sector.RefreshLights();
                //sector.RefreshMesh();
            }

            private void FullRefreshLights(MapSector sector)
            {
                //sector.ClearLightData();
                //sector.RefreshLights();
                //sector.RefreshMesh();
            }
        }
    }

    class SectorComparer : IComparer<MapSector>
    {
        Scene mScene;

        public SectorComparer(Scene scene)
        {
            mScene = scene;
        }

        public int Compare(MapSector a, MapSector b)
        {
            SceneCamera camera = mScene.SelectedCamera;
            Vector3 diffA = a.Position - camera.Position;
            Vector3 diffB = b.Position - camera.Position;
            int distA = (int)diffA.LengthSquared();
            int distB = (int)diffB.LengthSquared();
            return distA - distB;
        }
    }

}
